16. Quiz: ConnectProvider insert() Method

ContentProvider Insert() Method

INSTRUCTOR NOTE:

Replace the insert() method in the PetProvider class using this gist

Tip #1: PetDbHelper can be removed from the CatalogActivity class

Tip #2: Move the text in the toast messages to the strings.xml resource file, so they are not hardcoded into the Java file.

See ContentResolver insert example here

Insert Method

Implement and use PetProvider insert() method

SOLUTION:
  • Replace insert() method in PetProvider class with the code gist, linked below
  • Add the insertPet() method provided
  • Finish the TODO insertPet() helper method
  • Remove code in CatalogActivity and EditorActivity that queries the database directly
  • Instead, call the ContentResolver insert() method (which, in turn, will call the PetProvider insert() method) and receive a Uri result

Solution

First, you should have copied over the methods we provided you in this gist into the PetProvider class (replacing the insert() method you already had, and adding in the new insertPet() helper method).

In PetProvider.java:

public class PetProvider extends ContentProvider {

    …

    @Override
public Uri insert(Uri uri, ContentValues contentValues) {
    final int match = sUriMatcher.match(uri);
    switch (match) {
        case PETS:
            return insertPet(uri, contentValues);
        default:
            throw new IllegalArgumentException("Insertion is not supported for " + uri);
    }
}

/**
 * Insert a pet into the database with the given content values. Return the new content URI
 * for that specific row in the database.
 */
private Uri insertPet(Uri uri, ContentValues values) {

    // TODO: Insert a new pet into the pets database table with the given ContentValues

    // Once we know the ID of the new row in the table,
    // return the new URI with the ID appended to the end of it
    return ContentUris.withAppendedId(uri, id);
}

   …     
}

Since there’s a TODO in the insertPet() method, let’s zoom in on that next. We already know we’re in the PETS case from the UriMatcher result, so we need to continue walking down the diagram and get a database object, and then do the insertion.

Let’s start by getting a database object. Should it be a readable or writeable database? Well, we are editing the data source by adding a new pet, so we need to write changes to the database.

private Uri insertPet(Uri uri, ContentValues values) {
    // Get writeable database
    SQLiteDatabase database = mDbHelper.getWritableDatabase();

Then we need to do the database insertion. This will already be familiar to you because you already had practice inserting a pet directly into the SQLiteDatabase in the EditorActivity. Once we have a database object, we can call the insert() method on it, passing in the pet table name and the ContentValues object. The return value is the ID of the new row that was just created, in the form of a long data type (which can store numbers larger than the int data type).

private Uri insertPet(Uri uri, ContentValues values) {
    // Get writeable database    
    SQLiteDatabase database = mDbHelper.getWritableDatabase();

    // Insert the new pet with the given values
    long id = database.insert(PetEntry.TABLE_NAME, null, values);

Based on the ID, we can determine if the database operation went smoothly or not. If the ID is equal to -1, then we know the insertion failed. Otherwise, the insertion was successful. Hence, we add this check in the code. If the insertion failed, we log an error message using Log.e() and also return a null URI. That way, if a class tries to insert a pet, but receives a null URI, they’ll know that something went wrong.

private Uri insertPet(Uri uri, ContentValues values) {
    // Get writeable database
    SQLiteDatabase database = mDbHelper.getWritableDatabase();

    // Insert the new pet with the given values
    long id = database.insert(PetEntry.TABLE_NAME, null, values);
    // If the ID is -1, then the insertion failed. Log an error and return null.
    if (id == -1) {
        Log.e(LOG_TAG, "Failed to insert row for " + uri);
        return null;
    }

If this is the first Log statement you’ve added to the provider, make sure you add this import statement to the top of your provider file, so it knows what Log class you’re referring to.

import android.util.Log;

Since we’ll be logging multiple times throughout this file, it would be ideal to create a log tag as a global constant variable, so all log messages from the PetProvider will have the same log tag identifier when you are reading the system logs.

/** Tag for the log messages */
public static final String LOG_TAG = PetProvider.class.getSimpleName();

Ok back to the insertPet() method. If the insertion was successful, then we can add the row ID to the end of the pet URI (using the ContentUris.withAppendedId() method) to create a pet URI specific for the new pet. This code was already provided for you, so the whole insertPet() method with the TODO addressed, should look like the following.

In PetProvider.java:

private Uri insertPet(Uri uri, ContentValues values) {
    // Get writeable database
    SQLiteDatabase database = mDbHelper.getWritableDatabase();

    // Insert the new pet with the given values
    long id = database.insert(PetEntry.TABLE_NAME, null, values);
    // If the ID is -1, then the insertion failed. Log an error and return null.
    if (id == -1) {
        Log.e(LOG_TAG, "Failed to insert row for " + uri);
        return null;
    }

    // Return the new URI with the ID (of the newly inserted row) appended at the end
    return ContentUris.withAppendedId(uri, id);
}

Great work, now you’ve completed the PetProvider insert() functionality.

Use PetProvider insert() method

Let’s move onto the second half of the quiz, which is to USE the PetProvider insert() method by calling it from the UI code - the CatalogActivity and the EditorActivity. In those 2 activities, we will remove all references to the PetDbHelper and SQLiteDatabase objects, and interact solely with the ContentResolver using URIs.

In the CatalogActivity, when a user clicks on the “Insert Dummy Pet” menu item, we will call the ContentResolver insert() method with the pet content URI and a ContentValues object (containing info about Toto). Here is the updated version of the insertPet() method, which is called from the onOptionsItemSelected() method when the menu item is clicked on.

In CatalogActivity.java:

/**
 * Helper method to insert hardcoded pet data into the database. For debugging purposes only.
 */
private void insertPet() {
    // Create a ContentValues object where column names are the keys,
    // and Toto's pet attributes are the values.
    ContentValues values = new ContentValues();
    values.put(PetEntry.COLUMN_PET_NAME, "Toto");
    values.put(PetEntry.COLUMN_PET_BREED, "Terrier");
    values.put(PetEntry.COLUMN_PET_GENDER, PetEntry.GENDER_MALE);
    values.put(PetEntry.COLUMN_PET_WEIGHT, 7);

    // Insert a new row for Toto into the provider using the ContentResolver.
    // Use the {@link PetEntry#CONTENT_URI} to indicate that we want to insert
    // into the pets database table.
    // Receive the new content URI that will allow us to access Toto's data in the future.
    Uri newUri = getContentResolver().insert(PetEntry.CONTENT_URI, values);
}

In this file, we also remove all references to PetDbHelper (the import statement, the global variable definition and initialization in the onCreate() method). We removed the reference to the SQLiteDatabase object in the insertPet() method too. For the full CatalogActivity.java file at this point, check out this link.

Before we move onto further UI code changes, make sure the app compiles and runs. Test that the “Insert Dummy Pet” menu item still works as before.

EditorActivity

Next, in the EditorActivity, we repeat the same task, with some slight variations. After we remove the references to PetDbHelper and SQLiteDatabase, we can call the ContentResolver insert() method with the pet content URI and a ContentValues object constructed from the user input fields. One major difference between the SQLiteDatabase insert() method and the ContentResolver insert() method is that one returns a row ID, while the other returns a Uri, respectively.

Since the ContentResolver returns a Uri, we modify the code to check if the Uri is null or not. Then we show a toast message telling the user if the insertion was successful or not. We use a generic toast message, leaving off details about the row ID / pet URI because those are internal details that only a developer would care about. Below is the full insertPet() method, or you can check out the whole EditorActivity file here.

In EditorActivity.java:

/**
 * Get user input from editor and save new pet into database.
 */
private void insertPet() {
    // Read from input fields
    // Use trim to eliminate leading or trailing white space
    String nameString = mNameEditText.getText().toString().trim();
    String breedString = mBreedEditText.getText().toString().trim();
    String weightString = mWeightEditText.getText().toString().trim();
    int weight = Integer.parseInt(weightString);

    // Create a ContentValues object where column names are the keys,
    // and pet attributes from the editor are the values.
    ContentValues values = new ContentValues();
    values.put(PetEntry.COLUMN_PET_NAME, nameString);
    values.put(PetEntry.COLUMN_PET_BREED, breedString);
    values.put(PetEntry.COLUMN_PET_GENDER, mGender);
    values.put(PetEntry.COLUMN_PET_WEIGHT, weight);

    // Insert a new pet into the provider, returning the content URI for the new pet.
    Uri newUri = getContentResolver().insert(PetEntry.CONTENT_URI, values);

    // Show a toast message depending on whether or not the insertion was successful
    if (newUri == null) {
        // If the new content URI is null, then there was an error with insertion.
        Toast.makeText(this, getString(R.string.editor_insert_pet_failed),
                Toast.LENGTH_SHORT).show();
    } else {
        // Otherwise, the insertion was successful and we can display a toast.
        Toast.makeText(this, getString(R.string.editor_insert_pet_successful),
                Toast.LENGTH_SHORT).show();
    }
}

Make sure that you move the user-visible strings into the strings.xml file for localization purposes.

In res/values/strings.xml:

<!-- Toast message in editor when new pet has been successfully inserted [CHAR LIMIT=NONE] -->
<string name="editor_insert_pet_successful">Pet saved</string>

<!-- Toast message in editor when new pet has failed to be inserted [CHAR LIMIT=NONE] -->
<string name="editor_insert_pet_failed">Error with saving pet</string>

Run the app and test that creating a new pet still works correctly. If so, congratulations! You implemented insert() functionality in the ContentProvider and updated the UI code to call into that provider code!